﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Microsoft.Goldenlight.Extensions
{
    /// <summary>
    /// 	Extension methods for all kinds of (typed) enumerable data (Array, List, ...)
    /// </summary>
    public static class EnumerableExtensions
    {
        /// <summary>
        /// 	Performs an action for each item in the enumerable
        /// </summary>
        /// <typeparam name="T"> The enumerable data type </typeparam>
        /// <param name="source"> The data source. </param>
        /// <param name="action"> The action to be performed. </param>
        /// <example>
        /// 	var source = new[] { "1", "2", "3" }; source.ConvertList&lt;string, int&gt;().ForEach(Console.WriteLine);
        /// </example>
        /// <remarks>
        /// 	This method was intended to return the passed source to provide method chaining. Howver due to defered execution the compiler would actually never run the entire code at all.
        /// </remarks>
        public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
        {
            foreach (var value in source)
            {
                action(value);
            }
        }

        ///<summary>
        ///	Returns enumerable object based on source, which does not contains null references. If source is null reference, returns empty enumerable object.
        ///</summary>
        ///<typeparam name="T"> Type of source in source. </typeparam>
        ///<param name="source"> Target enumerable object. Can be null. </param>
        ///<example>
        ///	object[] source = null; foreach(var item in source.NotNull()){ // result of source.NotNull() is empty but not null enumerable } object[] source = new object[]{ null, "Hello World!", null, "Good bye!" }; foreach(var item in source.NotNull()){ // result of source.NotNull() is enumerable with two strings }
        ///</example>
        ///<remarks>
        ///	Contributed by tencokacistromy, http://www.codeplex.com/site/users/view/tencokacistromy
        ///</remarks>
        public static IEnumerable<T> IgnoreNulls<T>(this IEnumerable<T> source)
        {
            if (ReferenceEquals(source, null))
            {
                yield break;
            }
            foreach (var value in source.Where(item => !ReferenceEquals(item, null)))
            {
                yield return value;
            }
        }

        /// <summary>
        /// 	Returns the maximum item based on a provided selector.
        /// </summary>
        /// <typeparam name="T"> The item type </typeparam>
        /// <typeparam name="TValue"> The value item </typeparam>
        /// <param name="source"> The source. </param>
        /// <param name="selector"> The selector. </param>
        /// <param name="maxValue"> The max value as output parameter. </param>
        /// <returns> The maximum item </returns>
        /// <example>
        /// 	<code>int age;
        /// 		var oldestPerson = persons.Max(p =&gt; p.Age, out age);</code>
        /// </example>
        public static T Max<T, TValue>(this IEnumerable<T> source, Func<T, TValue> selector, out TValue maxValue) where T : class where TValue : IComparable
        {
            T maxItem = null;
            maxValue = default(TValue);
            foreach (var value in source)
            {
                if (value == null)
                {
                    continue;
                }
                var itemValue = selector(value);
                if (maxItem != null && itemValue.CompareTo(maxValue) <= 0)
                {
                    continue;
                }
                maxValue = itemValue;
                maxItem = value;
            }
            return maxItem;
        }

        /// <summary>
        /// 	Returns the maximum item based on a provided selector.
        /// </summary>
        /// <typeparam name="T"> The item type </typeparam>
        /// <typeparam name="TValue"> The value item </typeparam>
        /// <param name="source"> The source. </param>
        /// <param name="selector"> The selector. </param>
        /// <returns> The maximum item </returns>
        /// <example>
        /// 	<code>var oldestPerson = persons.Max(p =&gt; p.Age);</code>
        /// </example>
        public static T Max<T, TValue>(this IEnumerable<T> source, Func<T, TValue> selector) where T : class where TValue : IComparable
        {
            TValue maxValue;
            return source.Max(selector, out maxValue);
        }

        /// <summary>
        /// 	Returns the minimum item based on a provided selector.
        /// </summary>
        /// <typeparam name="T"> The item type </typeparam>
        /// <typeparam name="TValue"> The value item </typeparam>
        /// <param name="source"> The source. </param>
        /// <param name="selector"> The selector. </param>
        /// <param name="minValue"> The min value as output parameter. </param>
        /// <returns> The minimum item </returns>
        /// <example>
        /// 	<code>int age;
        /// 		var youngestPerson = persons.Min(p =&gt; p.Age, out age);</code>
        /// </example>
        public static T Min<T, TValue>(this IEnumerable<T> source, Func<T, TValue> selector, out TValue minValue) where T : class where TValue : IComparable
        {
            T minItem = null;
            minValue = default(TValue);
            foreach (var item in source)
            {
                if (item == null)
                {
                    continue;
                }
                var itemValue = selector(item);
                if ((minItem != null) && (itemValue.CompareTo(minValue) >= 0))
                {
                    continue;
                }
                minValue = itemValue;
                minItem = item;
            }
            return minItem;
        }

        /// <summary>
        /// 	Returns the minimum item based on a provided selector.
        /// </summary>
        /// <typeparam name="T"> The item type </typeparam>
        /// <typeparam name="TValue"> The value item </typeparam>
        /// <param name="source"> The source. </param>
        /// <param name="selector"> The selector. </param>
        /// <returns> The minimum item </returns>
        /// <example>
        /// 	<code>var youngestPerson = persons.Min(p =&gt; p.Age);</code>
        /// </example>
        public static T Min<T, TValue>(this IEnumerable<T> source, Func<T, TValue> selector) where T : class where TValue : IComparable
        {
            TValue minValue;
            return source.Min(selector, out minValue);
        }

        ///<summary>
        ///	Get Distinct
        ///</summary>
        ///<param name="source"> </param>
        ///<param name="expression"> </param>
        ///<typeparam name="T"> </typeparam>
        ///<typeparam name="TKey"> </typeparam>
        ///<returns> </returns>
        ///<remarks>
        ///	Contributed by Michael T, http://about.me/MichaelTran
        ///</remarks>
        public static IEnumerable<T> Distinct<T, TKey>(this IEnumerable<T> source, Func<T, TKey> expression)
        {
            return source == null ? Enumerable.Empty<T>() : source.GroupBy(expression).Select(i => i.First());
        }

        /// <summary>
        /// 	Removes matching source from a sequence
        /// </summary>
        /// <typeparam name="T"> </typeparam>
        /// <param name="source"> The source. </param>
        /// <param name="predicate"> The predicate. </param>
        /// <returns> </returns>
        /// <remarks>
        /// 	Renamed by James Curran, to match corresponding HashSet.RemoveWhere()
        /// </remarks>
        public static IEnumerable<T> RemoveWhere<T>(this IEnumerable<T> source, Predicate<T> predicate)
        {
            if (source == null)
            {
                yield break;
            }
            foreach (var item in source.Where(item => !predicate(item)))
            {
                yield return item;
            }
        }

        ///<summary>
        ///	Turn the list of objects to a string of Common Seperated Value
        ///</summary>
        ///<param name="source"> </param>
        ///<param name="separator"> </param>
        ///<typeparam name="T"> </typeparam>
        ///<returns> </returns>
        ///<example>
        ///	<code>var source = new[] { 1, 2, 3, 4, 5 };
        ///		string csv = source.ToCSV(';');</code>
        ///</example>
        ///<remarks>
        ///	Contributed by Moses, http://mosesofegypt.net
        ///</remarks>
        public static string ToCsv<T>(this IEnumerable<T> source, char separator)
        {
            if (source == null)
            {
                return string.Empty;
            }
            var csv = new StringBuilder();
            source.ForEach(value => csv.AppendFormat("{0}{1}", value, separator));
            return csv.ToString(0, csv.Length - 1);
        }

        ///<summary>
        ///	Turn the list of objects to a string of Common Seperated Value
        ///</summary>
        ///<param name="source"> </param>
        ///<typeparam name="T"> </typeparam>
        ///<returns> </returns>
        ///<example>
        ///	<code>var source = new[] {1, 2, 3, 4, 5};
        ///		string csv = source.ToCSV();</code>
        ///</example>
        ///<remarks>
        ///	Contributed by Moses, http://mosesofegypt.net
        ///</remarks>
        public static string ToCsv<T>(this IEnumerable<T> source)
        {
            return source.ToCsv(',');
        }

        /// <summary>
        /// 	Returns true if the <paramref name="source" /> is null or without any source.
        /// </summary>
        public static bool IsNullOrEmpty<T>(this IEnumerable<T> source)
        {
            return source == null || !source.Any();
        }

        /// <summary>
        /// 	Returns true if the <paramref name="source" /> is contains at least one item.
        /// </summary>
        public static bool IsNotEmpty<T>(this IEnumerable<T> source)
        {
            return !source.IsNullOrEmpty();
        }

        /// <summary>
        /// 	Appends an element to the end of the current collection and returns the new collection.
        /// </summary>
        /// <typeparam name="T"> The enumerable data type </typeparam>
        /// <param name="source"> The data source. </param>
        /// <param name="item"> The element to append the current collection with. </param>
        /// <returns> The modified collection. </returns>
        /// <example>
        /// 	var integers = Enumerable.Range(0, 3); // 0, 1, 2 integers = integers.Append(3); // 0, 1, 2, 3
        /// </example>
        public static IEnumerable<T> Append<T>(this IEnumerable<T> source, T item)
        {
            foreach (var value in source)
            {
                yield return value;
            }

            yield return item;
        }

        /// <summary>
        /// 	Prepends an element to the start of the current collection and returns the new collection.
        /// </summary>
        /// <typeparam name="T"> The enumerable data type </typeparam>
        /// <param name="source"> The data source. </param>
        /// <param name="item"> The element to prepend the current collection with. </param>
        /// <returns> The modified collection. </returns>
        /// <example>
        /// 	var integers = Enumerable.Range(1, 3); // 1, 2, 3 integers = integers.Prepend(0); // 0, 1, 2, 3
        /// </example>
        public static IEnumerable<T> Prepend<T>(this IEnumerable<T> source, T item)
        {
            yield return item;

            foreach (var value in source)
            {
                yield return value;
            }
        }

        /// <summary>
        /// 	Creates an Array from an IEnumerable&lt;T&gt; using the specified transform function.
        /// </summary>
        /// <typeparam name="T"> The source data type </typeparam>
        /// <typeparam name="TResult"> The source data type </typeparam>
        /// <param name="source"> The source data. </param>
        /// <param name="selector"> A transform function to apply to each element. </param>
        /// <returns> An Array of the source data type </returns>
        /// <example>
        /// 	var integers = Enumerable.Range(1, 3); var intStrings = source.ToArray(i => i.ToString());
        /// </example>
        /// <remarks>
        /// 	This method is a shorthand for the frequently use pattern IEnumerable&lt;T&gt;.Select(Func).ToArray()
        /// </remarks>
        public static TResult[] ToArray<T, TResult>(this IEnumerable<T> source, Func<T, TResult> selector)
        {
            return source.Select(selector).ToArray();
        }

        /// <summary>
        /// 	Creates a List&lt;T&gt; from an IEnumerable&lt;T&gt; using the specified transform function.
        /// </summary>
        /// <typeparam name="TSource"> The source data type </typeparam>
        /// <typeparam name="TResult"> The source data type </typeparam>
        /// <param name="source"> The source data. </param>
        /// <param name="selector"> A transform function to apply to each element. </param>
        /// <returns> An IEnumerable&lt;T&gt; of the source data type </returns>
        /// <example>
        /// 	var integers = Enumerable.Range(1, 3); var intStrings = source.ToList(i => i.ToString());
        /// </example>
        /// <remarks>
        /// 	This method is a shorthand for the frequently use pattern IEnumerable&lt;T&gt;.Select(Func).ToList()
        /// </remarks>
        public static List<TResult> ToList<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
        {
            return source.Select(selector).ToList();
        }

        /// <summary>
        /// 	Computes the sum of a sequence of UInt32 source.
        /// </summary>
        /// <param name="source"> A sequence of UInt32 source to calculate the sum of. </param>
        /// <returns> The sum of the source in the sequence. </returns>
        public static uint Sum(this IEnumerable<uint> source)
        {
            return source.Aggregate(0U, (current, number) => current + number);
        }

        /// <summary>
        /// 	Computes the sum of a sequence of UInt64 source.
        /// </summary>
        /// <param name="source"> A sequence of UInt64 source to calculate the sum of. </param>
        /// <returns> The sum of the source in the sequence. </returns>
        public static ulong Sum(this IEnumerable<ulong> source)
        {
            return source.Aggregate(0UL, (current, number) => current + number);
        }

        /// <summary>
        /// 	Computes the sum of a sequence of nullable UInt32 source.
        /// </summary>
        /// <param name="source"> A sequence of nullable UInt32 source to calculate the sum of. </param>
        /// <returns> The sum of the source in the sequence. </returns>
        public static uint? Sum(this IEnumerable<uint?> source)
        {
            return source.Where(nullable => nullable.HasValue).Aggregate(0U, (current, nullable) => current + nullable.GetValueOrDefault());
        }

        /// <summary>
        /// 	Computes the sum of a sequence of nullable UInt64 source.
        /// </summary>
        /// <param name="source"> A sequence of nullable UInt64 source to calculate the sum of. </param>
        /// <returns> The sum of the source in the sequence. </returns>
        public static ulong? Sum(this IEnumerable<ulong?> source)
        {
            return source.Where(nullable => nullable.HasValue).Aggregate(0UL, (current, nullable) => current + nullable.GetValueOrDefault());
        }

        /// <summary>
        /// 	Computes the sum of a sequence of UInt32 source that are obtained by invoking a transformation function on each element of the intput sequence.
        /// </summary>
        /// <param name="source"> A sequence of source that are used to calculate a sum. </param>
        /// <param name="selection"> A transformation function to apply to each element. </param>
        /// <returns> The sum of the projected source. </returns>
        public static uint Sum<T>(this IEnumerable<T> source, Func<T, uint> selection)
        {
            return source.Select(selection).Sum();
        }

        /// <summary>
        /// 	Computes the sum of a sequence of nullable UInt32 source that are obtained by invoking a transformation function on each element of the intput sequence.
        /// </summary>
        /// <param name="source"> A sequence of source that are used to calculate a sum. </param>
        /// <param name="selection"> A transformation function to apply to each element. </param>
        /// <returns> The sum of the projected source. </returns>
        public static uint? Sum<T>(this IEnumerable<T> source, Func<T, uint?> selection)
        {
            return source.Select(selection).Sum();
        }

        /// <summary>
        /// 	Computes the sum of a sequence of UInt64 source that are obtained by invoking a transformation function on each element of the intput sequence.
        /// </summary>
        /// <param name="source"> A sequence of source that are used to calculate a sum. </param>
        /// <param name="selector"> A transformation function to apply to each element. </param>
        /// <returns> The sum of the projected source. </returns>
        public static ulong Sum<T>(this IEnumerable<T> source, Func<T, ulong> selector)
        {
            return source.Select(selector).Sum();
        }

        /// <summary>
        /// 	Computes the sum of a sequence of nullable UInt64 source that are obtained by invoking a transformation function on each element of the intput sequence.
        /// </summary>
        /// <param name="source"> A sequence of source that are used to calculate a sum. </param>
        /// <param name="selector"> A transformation function to apply to each element. </param>
        /// <returns> The sum of the projected source. </returns>
        public static ulong? Sum<T>(this IEnumerable<T> source, Func<T, ulong?> selector)
        {
            return source.Select(selector).Sum();
        }

        /// <summary>
        /// 	Converts an enumeration of groupings into a Dictionary of those groupings.
        /// </summary>
        /// <typeparam name="TKey"> Key type of the grouping and dictionary. </typeparam>
        /// <typeparam name="TValue"> Element type of the grouping and dictionary list. </typeparam>
        /// <param name="groupings"> The enumeration of groupings from a GroupBy() clause. </param>
        /// <returns> A dictionary of groupings such that the key of the dictionary is TKey type and the value is List of TValue type. </returns>
        public static Dictionary<TKey, List<TValue>> ToDictionary<TKey, TValue>(this IEnumerable<IGrouping<TKey, TValue>> groupings)
        {
            return groupings.ToDictionary(group => group.Key, group => group.ToList());
        }
    }
}